﻿#region Using Statements
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
#endregion

namespace GameBase.Sounds
{
    /// <summary>
    /// Defines properties and methods for management of sound playback.
    /// </summary>
    public abstract class SoundManager : IDisposable
    {
        #region XACT Audio Fields

        AudioEngine audioEngine;
        WaveBank waveBank;
        SoundBank soundBank;

        #endregion

        #region Cue Management

        // list with all previously used cue instances...
        readonly List<Cue> playingCueList;

        // list with finished cues ready to delete...
        // this helps to avoid keeping unnecessary instances of used cues
        readonly List<Cue> deleteCueList;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of this class.
        /// </summary>
        /// <param name="contentPath">Path of XACT audio related content.</param>
        /// <param name="xactProjectName">XACT global settings file name - .xgs</param>
        /// <param name="waveBankName">Wave bank name - .xwb</param>
        /// <param name="soundBankName">Sound bank name - .xsb</param>
        public SoundManager(string contentPath, string xactProjectName, string waveBankName, string soundBankName)
        {
            // initialize XACT audio fields
            audioEngine = new AudioEngine(contentPath + xactProjectName + ".xgs");
            waveBank = new WaveBank(audioEngine, contentPath + waveBankName + ".xwb");
            soundBank = new SoundBank(audioEngine, contentPath + soundBankName + ".xsb");

            // initialize cue management
            playingCueList = new List<Cue>();
            deleteCueList = new List<Cue>();
        }

        #endregion

        #region Dispose

        // dispose flag
        bool isDisposed = false;

        /// <summary>
        /// Immediately releases all resources used by this object.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Allows an object to try to free resources and perform other cleanup operations before it is reclaimed by garbage collection.
        /// </summary>
        ~SoundManager()
        {
            Dispose(false);
        }

        /// <summary>
        /// Releases the unmanaged resources used by this object and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!isDisposed)
            {
                if (disposing)
                {
                    // cleanup managed resources
                    if (audioEngine != null) { audioEngine.Dispose(); audioEngine = null; }
                    if (waveBank != null) { waveBank.Dispose(); waveBank = null; }
                    if (soundBank != null) { soundBank.Dispose(); soundBank = null; }

                    foreach (Cue cue in playingCueList)
                        cue.Dispose();
                    playingCueList.Clear();
                    deleteCueList.Clear();
                }

                isDisposed = true;
            }
        }

        #endregion

        #region Update

        /// <summary>
        /// Performs periodic work required by the sound manager, and must be called once per framwe.
        /// </summary>
        /// <param name="gameTime">Time passed since the last call to this method.</param>
        public void Update(GameTime gameTime)
        {
            audioEngine.Update();

            // delete any finished cue
            deleteCueList.Clear();
            foreach (Cue cue in playingCueList)
            {
                if (cue.IsStopped) { deleteCueList.Add(cue); }
            }
            foreach (Cue cue in deleteCueList)
            {
                playingCueList.Remove(cue);
                cue.Dispose();
            }
        }

        #endregion

        #region Sound Configuration

        /// <summary>
        /// Sets the value of a global variable.
        /// </summary>
        /// <param name="name">Friendly name of the global variable.</param>
        /// <param name="value">Value to assign to the global variable.</param>
        protected void SetGlobalVariable(string name, float value)
        {
            audioEngine.SetGlobalVariable(name, value);
        }

        #endregion

        #region PlayCue, PlayCue3D

        #region CueInstanceVariable

        /// <summary>
        /// Stores the name and value for a cue-instance variable.
        /// </summary>
        protected struct CueInstanceVariable
        {
            // backing fields
            string name;
            float value;

            /// <summary>
            /// Friendly name of the variable.
            /// </summary>
            public string Name { get { return name; } }

            /// <summary>
            /// Value of the variable.
            /// </summary>
            public float Value { get { return value; } }

            /// <summary>
            /// Sets the configuration for a cue-instance variable.
            /// </summary>
            /// <param name="name">Friendly name of the variable to set.</param>
            /// <param name="value">Value to assign to the variable.</param>
            public CueInstanceVariable(string name, float value)
            {
                this.name = name;
                this.value = value;
            }
        }

        #endregion

        /// <summary>
        /// Requests playback of a specific cue, as fire and forget.
        /// <para>To let the cue play more than one sound at a time, don't limit its number of instances.</para>
        /// </summary>
        /// <param name="cueName">Cue friedly name.</param>
        protected void PlayCue(string cueName)
        {
            Cue cue = soundBank.GetCue(cueName);
            playingCueList.Add(cue);

            cue.Play();
        }

        /// <summary>
        /// Requests playback of a specific cue, as fire and forget.
        /// <para>To let the cue play more than one sound at a time, don't limit its number of instances.</para>
        /// </summary>
        /// <param name="cueName">Cue friedly name.</param>
        /// <param name="variableList">List of cue-instance variables to configure before playback.</param>
        protected void PlayCue(string cueName, List<CueInstanceVariable> variableList)
        {
            Cue cue = soundBank.GetCue(cueName);
            playingCueList.Add(cue);

            foreach (CueInstanceVariable auxVar in variableList)
            {
                cue.SetVariable(auxVar.Name, auxVar.Value);
            }

            cue.Play();
        }

        /// <summary>
        /// Requests playback of a specific cue, applying the corresponding 3D audio values.
        /// </summary>
        /// <param name="cueName">Cue friedly name.</param>
        /// <param name="lstPos">Listener position.</param>
        /// <param name="emtPos">Emitter position.</param>
        protected void PlayCue3D(string cueName, Vector3 lstPos, Vector3 emtPos)
        {
            AudioListener listener = new AudioListener() { Position = lstPos };
            AudioEmitter emitter = new AudioEmitter() { Position = emtPos };

            Cue cue = soundBank.GetCue(cueName);
            playingCueList.Add(cue);

            cue.Apply3D(listener, emitter);
            cue.Play();
        }

        #endregion

        #region PlayAllSounds, PauseAllSounds, StopAllSounds

        /// <summary>
        /// Resumes playback of all sounds.
        /// </summary>
        public virtual void PlayAllSounds()
        {
            foreach (Cue cue in playingCueList)
            {
                cue.Resume();
            }
        }

        /// <summary>
        /// Pauses playback of all sounds.
        /// </summary>
        public virtual void PauseAllSounds()
        {
            foreach (Cue cue in playingCueList)
            {
                cue.Pause();
            }
        }

        /// <summary>
        /// Stops playback of all sounds.
        /// </summary>
        public virtual void StopAllSounds()
        {
            foreach (Cue cue in playingCueList)
            {
                cue.Stop(AudioStopOptions.Immediate);
            }
        }

        #endregion

        #region GetCue

        /// <summary>
        /// Gets a new instance of a specific cue.
        /// <para>It allows more control over that cue.</para>
        /// </summary>
        /// <param name="cueName">Cue friendly name.</param>
        /// <returns>Cue object.</returns>
        protected Cue GetCue(string cueName)
        {
            return soundBank.GetCue(cueName);
        }

        #endregion
    }
}
